﻿/**
Library: Alternative Transitioning
Author: Valentyn Derkach 2007.07.04
———————————————————————————————————————————————————————————————————————————————————————————————————————————————————
2008.04.23
	Added isExecuting and isRunning getters.
	Replaced 'transition' property with the 'name'.
	Added setProcessor, setSubject and setProperties methods.
	Changed processor, subject and properties setters - switched to read and write.
	Removed reStart method.
	Renamed restartProcessor method and arguments to rebootProcessor.
	Renamed completeTransitionInternal into doCompleteTransition.
	Renamed completeTransition into stop.
	Added canStart method.
	Changed start method:
		- added subject, properties and processor parameters
		- added isStart call
		- iterationFunction has been moved outside and became a private method doIteration
	
	TODO:
		- implement processor locking during the transition's execution.
		- check the necessity of the completeEvent, executeEvent, interrupeEvent variables
/**/
package com.bizmlib.transitioning
{

	import flash.events.EventDispatcher;
	import flash.utils.*;
	import com.bizmlib.transitioning.ITweenProcessor;
	import com.bizmlib.transitioning.TransitioningEvent;


	public class Transitioning extends EventDispatcher
	{
		public static const STATE_STILL:Number = 0;
		public static const STATE_RUNNING:Number = 1;
		
		
		public var test:String = "Transitioning";
		private static var itemsList:Transitioning = null;
		private static var __commonFPS:Number = 40;
		private static var defaultIntervalValue:Number = 20;
		/**
			tintObjects
			│
			├item
			│	├threadID
			│	├transition
			│	├targetObject
			│	├properties
			│	├next
			│	└prev
			├item
			│	└...
			└...
		/**/
		
		
		private var __fps:Number;
		private var threadID:Number = -1;
		private var __name:String = "unknown";
		private var __subject:Object;
		private var __properties:Object;
		private var __processor:ITweenProcessor;
		private var completeEvent:Boolean = true;
		private var executeEvent:Boolean = true;
		private var interruptEvent:Boolean = true;
		private var prev:Transitioning;
		private var next:Transitioning;
		private var iterationFunction:Function;
		private var stateRun:Number;


		// ———————————————————————————————————————————————————————————————————————————————————————————————————————————————————
		// CONSTRUCTOR. does nothing
		public function Transitioning(subject:Object = undefined, properties:Object = undefined, processor:ITweenProcessor = undefined){
			//trace("Transitioning(" + arguments + ")");

			super();
			
			stateRun = STATE_STILL;

			//trace("Transitioning()\t" + speedObject.speed + ", " + speedObject.dstSpeed);

			//if(itemsList==undefined){itemsList=null;}

			init(subject, properties, processor);
		}
		// ———————————————————————————————————————————————————————————————————————————————————————————————————————————————————


		// ———————————————————————————————————————————————————————————————————————————————————————————————————————————————————
		// PROPERTIES
		public static function set commonFPS(value:Number):void{
			__commonFPS = Math.abs(value) || __commonFPS;
		}
		public static function get commonFPS():Number{return __commonFPS;}
		public function set fps(value:Number):void{
			__fps=Math.abs(value) || null;
		}
		public function get fps():Number{return __fps;}
		/**
		 * The container of properties names and their final values, which need to be reached by the end of the transition's execution.
		 * @return	Object
		 */
		public function set properties(value:Object):void
		{
			setProperties(value);
		}
		public function get properties():Object
		{
			return __properties;
		}
		/**
		 * The object to which the transition applies.
		 * @return	Object
		 */
		public function set subject(value:Object):void
		{
			setSubject(value);
		}
		public function get subject():Object
		{
			return __subject;
		}
		/**
		 * The processor which does all calculations.
		 * @return	ITweenProcessor
		 */
		public function set processor(value:ITweenProcessor):void
		{
			setProcessor(value);
		}
		public function get processor():ITweenProcessor
		{
			return __processor;
		}
		public function get tweenProcessor():ITweenProcessor{return processor;}
		
		/**
		 * Returns the name of the transition, or in other words the type of the transition.
		 * @return	String
		 */
		public function get name():String
		{
			return __name;
		}
		
		/**
		 * Identifies whether the transition is running or not. Read-only. The same as isRunning property.
		 * @return	Boolean
		 */
		public function get isExecuting():Boolean
		{
			//return threadID==-1?false:true;
			return isRunning;
		}
		/**
		 * Identifies whether the transition is running or not. Read-only. The same as isExecuting property.
		 * @return	Boolean
		 */
		public function get isRunning():Boolean
		{
			return stateRun==STATE_RUNNING;
		}
		// ———————————————————————————————————————————————————————————————————————————————————————————————————————————————————
		
		
		
		// ———————————————————————————————————————————————————————————————————————————————————————————————————————————————————
		/**
		 * Changes the subject, properties, processor properties and also reboots the processor. You don't necesserily have to use this method. Consider about
		 * using the setters for the above properties and rebootProcessor method instead. Also you can set those values directly in the start call.
		 */
		final public function reInit(subject:Object = undefined, properties:Object = undefined, processor:ITweenProcessor = undefined, rebootProcessor:Boolean = false):void
		{
			//trace(this + ".reInit(" + arguments + ")");
			
			init(subject, properties, processor);
			if(rebootProcessor)
			{
				this.rebootProcessor();
			}
		}
		
		/**
		 * Resets the dynamic values of the processor.
		 */
		final public function rebootProcessor():void
		{
			processor.reInit();
		}
		
		/**
		 * Starts the transition's execution. 
		 */
		final public function start(subject:Object = undefined, properties:Object = undefined, processor:ITweenProcessor = undefined, rebootProcessor:Boolean = false):void
		{
			//trace(this + ".start(" + arguments + ")");
			
			if(!canStart(true))
			{
				return;
			}
			
			if(threadID<0)
			{
				//closeTransition(subject, transition);
				addTransitionInTheList(this);
			}
			else
			{
				clearInterval(threadID);
				threadID = -1;
			}
			
			init(subject, properties, processor);
			if(rebootProcessor)
			{
				this.rebootProcessor();
			}
			
			onStart();
			
			threadID = setInterval(doIteration, Math.round(1000/(fps || commonFPS)));
			doIteration();
		}
		
		/**
		 * Stops the transition's execution.
		 */
		final public function stop():void
		{
			doCompleteTransition(false);
		}
		// ———————————————————————————————————————————————————————————————————————————————————————————————————————————————————

		
		
		// ———————————————————————————————————————————————————————————————————————————————————————————————————————————————————
		final protected function setName(newName:String = undefined):void
		{
			__name = newName || name;
		}
		private function init(subject:Object = undefined, properties:Object = undefined, processor:ITweenProcessor = undefined):void
		{
			//trace(this + ".init(" + arguments + ")");
			
			setSubject(subject);
			setProperties(properties);
			setProcessor(processor);
		}
		private function setSubject(subject:Object = undefined):void
		{
			if(!subject)
			{
				return;
			}
			
			if(isRunning)
			{
				throw new Error(this + " can't set subject while the transition is running");
				return;
			}
			
			__subject = subject;
		}
		private function setProperties(properties:Object = undefined):void
		{
			if(!properties && !((properties is Number) && !isNaN(properties as Number)))
			{
				return;
			}
			
			if(isRunning)
			{
				throw new Error(this + " can't set properties while the transition is running");
				return;
			}
			
			__properties = properties;
		}
		private function setProcessor(processor:ITweenProcessor = undefined):void
		{
			if(!processor)
			{
				return;
			}
			
			if(isRunning)
			{
				throw new Error(this + " can't set processor while the transition is running");
				return;
			}
						
			__processor = processor;
		}
		private static function addTransitionInTheList(controller:Transitioning):void
		{
			//trace("Transitioning.addTransitionInTheList(" + arguments + ")");
			
			controller.next = itemsList;
			controller.prev = null;
			if(itemsList)
			{
				itemsList.prev = controller;
			}
			itemsList = controller;
		}
		private function broadcastEvent(eventName:String):void
		{
			dispatchEvent(new TransitioningEvent(eventName, subject, properties, name));
		}
		
		private function doIteration():void
		{
			//trace(this + ".doIteration(" + arguments + ")");
			
			processor.onBeforeIteration();
			transitionIteration();
			broadcastEvent(TransitioningEvent.EXECUTION);
			
			if(processor.isFinal())
			{
				doCompleteTransition(true);
			}
			processor.onAfterIteration();
		}
		/**
		 * Finds whether it's possible to start the transition with the current configuration or not.
		 */
		private function canStart(throwError:Boolean = false):Boolean
		{
			var currentTransition:Transitioning = itemsList as Transitioning;
			while(currentTransition)
			{
				if(currentTransition!=this && currentTransition.processor==processor)
				{
					if(throwError)
					{
						throw new Error(this + " can't start - the same processor is already in use by another transition.");
					}
					return false;
				}
				currentTransition = currentTransition.next;
			}
			return true;
		}
		protected function onStart():void
		{
			// this is an empty method that is called from within the start method
		}
		// ———————————————————————————————————————————————————————————————————————————————————————————————————————————————————



		// ———————————————————————————————————————————————————————————————————————————————————————————————————————————————————
		// TRANSITION REMOVAL. finds the transition controller(s) using movieClip and transitionName and removes it
		/**
		public static function closeTransition(subject:Object, transitionName:String):void
		{
			return;
			var curController:Object = itemsList;
			while(curController!=null){
				if(curController.subject==subject && curController.transition==transitionName){
					var nextController:Object = curController.next;
					curController.completeTransition();
					curController = nextController;
				}else{
					curController = curController.next;
				}
			}
		}
		/**/
		// ———————————————————————————————————————————————————————————————————————————————————————————————————————————————————






		// ———————————————————————————————————————————————————————————————————————————————————————————————————————————————————
		// TRANSITION COMPLETION. every transition method should call this after the transition is finished. this method
		// removes the transition controller object from the list and stops the execution of the transition thread
		private function doCompleteTransition(broadcastCompleteEvent:Boolean):void
		{	
			clearInterval(threadID);
			threadID = -1;

			if(next)
			{
				next.prev = prev;
			}
			if(prev!=null)
			{
				prev.next = next;
			}
			else
			{
				itemsList = next;
			}

			if(completeEvent && broadcastCompleteEvent)
			{
				broadcastEvent(TransitioningEvent.COMPLETED);
			}

			/**
			if(arguments.caller==transitionIteration){
				//trace("completeEvent=" + completeEvent);
				if(completeEvent){broadcastEvent(TransitioningEvent.TYPE_COMPLETED);}
			}else{
				if(interruptEvent){broadcastEvent(TransitioningEvent.TYPE_INTERRUPT);}
			}
			/**/
		}
		// ———————————————————————————————————————————————————————————————————————————————————————————————————————————————————
		override public function toString():String
		{
			return "[Transitioning	name=" + name + "	subject=" + subject + "]";
		}


		// ———————————————————————————————————————————————————————————————————————————————————————————————————————————————————
		// TRANSITION ITERATION. is an empty transition iteration function. override it in your own transitioning class
		protected function transitionIteration():void
		{
			//trace(this + ".transitionIteration()");
			if(executeEvent){broadcastEvent(TransitioningEvent.TYPE_EXECUTION);}
		}
		// ———————————————————————————————————————————————————————————————————————————————————————————————————————————————————

	}
}